package com.hmdp.service.impl;

import cn.hutool.core.util.StrUtil;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.hmdp.common.Constants;
import com.hmdp.dto.Result;
import com.hmdp.dto.UserDTO;
import com.hmdp.entity.SeckillVoucher;
import com.hmdp.entity.VoucherOrder;
import com.hmdp.mapper.VoucherOrderMapper;
import com.hmdp.service.ISeckillVoucherService;
import com.hmdp.service.IVoucherOrderService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import com.hmdp.utils.IdGenerator;
import com.hmdp.utils.MessageObject;
import com.hmdp.utils.UserHolder;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.redisson.api.RLock;
import org.redisson.api.RedissonClient;
import org.redisson.client.RedisClient;
import org.springframework.amqp.core.ExchangeTypes;
import org.springframework.amqp.rabbit.annotation.Exchange;
import org.springframework.amqp.rabbit.annotation.Queue;
import org.springframework.amqp.rabbit.annotation.QueueBinding;
import org.springframework.amqp.rabbit.annotation.RabbitListener;
import org.springframework.amqp.rabbit.connection.CorrelationData;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.aop.framework.AopContext;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Lazy;
import org.springframework.core.io.ClassPathResource;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.atomic.AtomicInteger;

/**
 * <p>
 *  服务实现类
 * </p>
 *
 * @author 虎哥
 * @since 2021-12-22
 */
@Service
@Slf4j
@RequiredArgsConstructor
public class VoucherOrderServiceImpl extends ServiceImpl<VoucherOrderMapper, VoucherOrder> implements IVoucherOrderService {
    private final StringRedisTemplate stringRedisTemplate;

    private final IdGenerator idGenerator;

    private static final DefaultRedisScript<Long> SECKILL_SCRIPT;

    private final RabbitTemplate rabbitTemplate;

    private final ExecutorService SECKILL_ORDER_EXECUTOR = Executors.newFixedThreadPool(8);

    private final ISeckillVoucherService seckillVoucherService;

    private final RedissonClient redissonClient;

    AtomicInteger atomicInteger = new AtomicInteger(0);

    @Lazy
    @Autowired
    private IVoucherOrderService proxy;

    static {
        SECKILL_SCRIPT = new DefaultRedisScript<>();
        SECKILL_SCRIPT.setResultType(Long.class);
        SECKILL_SCRIPT.setLocation(new ClassPathResource("seckill.lua"));
    }

    @RabbitListener(bindings = @QueueBinding(
            value = @Queue(name = "voucher.order.queue"),
            exchange = @Exchange(name = "hmdp.topic", type = ExchangeTypes.TOPIC),
            key = "voucher.order.handle"))
    public void handleVoucherOrder(VoucherOrder voucherOrder) {
        log.info("收到MQ消息: {}", voucherOrder.getId());
        SECKILL_ORDER_EXECUTOR.submit(() -> {
            log.info("开始处理第{}条消息", atomicInteger);
            atomicInteger.addAndGet(1);
            proxy.createVoucherOrder(voucherOrder);
        });
    }

    @Transactional(rollbackFor = Exception.class)
    public void createVoucherOrder(VoucherOrder voucherOrder) {
        Long voucherId = voucherOrder.getVoucherId();
        Long userId = voucherOrder.getUserId();
        RLock lock = redissonClient.getLock("lock:order:" + userId);
        // 能到这里的说明都是下单成功的，因此修改库存时需要使用阻塞式锁，而不是没获取到就退出
        lock.lock();
        try {
            boolean isSuccess = seckillVoucherService.update()
                .setSql("stock = stock - 1")
                .eq("voucher_id", voucherId)
                .gt("stock", 0)
                .update();

            if (isSuccess) {
                proxy.save(voucherOrder);
            }
            else {
                log.warn("用户 {} 抢购券 {} 失败，请重试", userId, voucherId);
            }
        } finally {
            lock.unlock();
        }
    }
    
    @Override
    public Result seckillVoucher(Long voucherId) {
        UserDTO user = UserHolder.getUser();
        Long userId = user.getId();
        String stockKey = Constants.VOUCHER_STOCK_KEY + voucherId;
        String userSetKey = Constants.VOUCHER_USER_PURCHAS_KEY + voucherId;
        // 使用 hasKey 检查，注意 Boolean.FALSE.equals 的用法可以避免空指针
        if (Boolean.FALSE.equals(stringRedisTemplate.hasKey(stockKey))) {
            // Redis中没有库存信息，从数据库加载
            SeckillVoucher voucher = seckillVoucherService.getById(voucherId);
            if (voucher == null) {
                return Result.fail("优惠券不存在！");
            }
            // 将库存写入Redis
            stringRedisTemplate.opsForValue().set(stockKey, voucher.getStock().toString());
        }
        ArrayList<String> keyList = new ArrayList<>();
        keyList.add(userSetKey);
        keyList.add(stockKey);
        Long execute = stringRedisTemplate.execute(SECKILL_SCRIPT, keyList, userId.toString());

        if (execute != 0) {
            return execute == 2 ? Result.fail("请勿重复下单") : Result.fail("库存不足");
        }

        Long orderId = idGenerator.generateOrderId("seckill:voucherOder:" + voucherId);
        VoucherOrder voucherOrder = new VoucherOrder()
                .setVoucherId(voucherId)
                .setUserId(userId)
                .setId(orderId);

        String messageId = UUID.randomUUID().toString();
        MessageObject messageObject = new MessageObject()
                .setData(voucherOrder)
                .setStatus(0);
        stringRedisTemplate.opsForHash().put("order", messageId, messageObject.toString());
        CorrelationData correlationData = new CorrelationData(messageId);
        rabbitTemplate.convertAndSend("hmdp.topic", "voucher.order.handle", voucherOrder, correlationData);
        return Result.ok(orderId);
    }
}
